#include "HttpClient.h"
#include "FileUtil.h"
#include <curl/curl.h>

#ifndef WIN32
#define LINUX
#endif

// #define DEBUG_CURL

#define CLIENT_USERAGENT "httpclientcpp-agent/1.0"

// Logs messages
#define LOG_ERROR_EMPTY_HOST_MSG                "[Error] Empty hostname."
#define LOG_WARNING_OBJECT_NOT_CLEANED          "[Warning] Object was freed before calling " \
                                                "CleanupSession(). The API session was cleaned though."
#define LOG_ERROR_CURL_ALREADY_INIT_MSG         "[Error] Curl session is already initialized ! " \
                                                "Use CleanupSession() to clean the present one."
#define LOG_ERROR_CURL_NOT_INIT_MSG             "[Error] Curl session is not initialized ! Use InitSession() before."


#define LOG_ERROR_CURL_REQ_FAILURE_FORMAT       "[Error] Unable to perform request from '%s' " \
                                                "(Error = %d | %s) (HTTP_Status = %ld)"
#define LOG_ERROR_CURL_REST_FAILURE_FORMAT      "[Error] Unable to perform a REST request from '%s' " \
                                                "(Error = %d | %s)"
#define LOG_ERROR_CURL_DOWNLOAD_FAILURE_FORMAT  "[Error] Unable to perform a request - '%s' from '%s' " \
                                                "(Error = %d | %s) (HTTP_Status = %ld)"
#define LOG_ERROR_DOWNLOAD_FILE_FORMAT          "[Error] Unable to open local file %s"

namespace OCPP
{
    // STRING HELPERS

    /**
     * @brief returns a formatted string
     *
     * @param [in] strFormat string with one or many format specifiers
     * @param [in] parameters to be placed in the format specifiers of strFormat
     *
     * @retval string formatted string
     */
    String StringFormat(String strFormat, ...) {
        va_list args;
        va_start(args, strFormat);
        size_t len = std::vsnprintf(NULL, 0, strFormat.c_str(), args);
        va_end(args);
        std::vector<char> vec(len + 1);
        va_start(args, strFormat);
        std::vsnprintf(&vec[0], len + 1, strFormat.c_str(), args);
        va_end(args);
        return &vec[0];
    }

    /**
    * @brief stores the server response in an already opened file stream
    * used by DownloadFile()
    *
    * @param buff pointer of max size (size*nmemb) to read data from it
    * @param size size parameter
    * @param nmemb memblock parameter
    * @param userdata pointer to user data (file stream)
    *
    * @return (size * nmemb)
    */
    size_t WriteToFileCallback(void* buff, size_t size, size_t nmemb, void* data)
    {
        if ((size == 0) || (nmemb == 0) || ((size * nmemb) < 1) || (data == nullptr))
            return 0;

        std::ofstream* pFileStream = reinterpret_cast<std::ofstream*>(data);
        if (pFileStream->is_open())
        {
            pFileStream->write(reinterpret_cast<char*>(buff), size * nmemb);
        }

        return size * nmemb;
    }

    /**
     * @brief stores the server response in std::vector<char>
     *
     * @param buff pointer of max size (size*nmemb) to read data from it
     * @param size size parameter
     * @param nmemb memblock parameter
     * @param userdata pointer to user data (file stream)
     *
     * @return (size * nmemb)
     */
    size_t WriteToMemoryCallback(void* buff, size_t size, size_t nmemb, void* data) {
        if ((size == 0) || (nmemb == 0) || (data == nullptr)) return 0;

        auto* vec = reinterpret_cast<std::vector<unsigned char> *>(data);
        size_t ssize = size * nmemb;
        std::copy(reinterpret_cast<unsigned char*>(buff), reinterpret_cast<unsigned char*>(buff) + ssize,
            std::back_inserter(*vec));

        return ssize;
    }

    size_t WriteToMemoryCallback2(void* buff, size_t size, size_t nmemb, void* data) {
        if ((size == 0) || (nmemb == 0) || (data == nullptr)) return 0;

        String& destBuffer = *reinterpret_cast<String*>(data);
        size_t ssize = size * nmemb;

        destBuffer.append(reinterpret_cast<char*>(buff), ssize);

        return ssize;
    }

    size_t getContentLengthFunc(void* ptr, size_t size, size_t nmemb, void* stream)
    {
        int r;
        long len = 0;

        r = sscanf((const char*)ptr, "Content-Length: %ld\n", &len);
        if (r) /* Microsoft: we don't read the specs */
            *((long*)stream) = len;
        return size * nmemb;
    }

    class CurlHandle
    {
    public:
        static CurlHandle& instance();
        CurlHandle& operator=(CurlHandle const&) = delete;
        CurlHandle& operator=(CurlHandle&&) = delete;
        CurlHandle()
        {
            const auto eCode = curl_global_init(CURL_GLOBAL_ALL);
            if (eCode != CURLE_OK) {
                throw std::runtime_error{ "Error initializing libCURL" };
            }
        }

        ~CurlHandle() 
        { 
            curl_global_cleanup(); 
        }
    };

    CurlHandle& CurlHandle::instance() {
        static CurlHandle inst{};
        return inst;
    }

    // Static members initialization
    String HttpClient::s_strCertificationAuthorityFile;

    /**
     * @brief constructor of the HTTP client object
     *
     * @param Logger - a callabck to a logger function void(const String&)
     *
     */
    HttpClient::HttpClient(LogFnCallback Logger) :
        m_oLog(Logger),
        m_iCurlTimeout(0),
        m_bHTTPS(false),
        m_bNoSignal(false),
        m_bProgressCallbackSet(false),
        m_eSettingsFlags(ALL_FLAGS),
        m_pCurlSession(nullptr),
        m_pHeaderlist(nullptr),
        m_curlHandle(CurlHandle::instance())
    {

    }

    /**
     * @brief destructor of the HTTP client object
     *
     */
    HttpClient::~HttpClient()
    {
        if (m_pCurlSession != nullptr)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_WARNING_OBJECT_NOT_CLEANED);

            CleanupSession();
        }
    }

    String HttpClient::UserAgent;
    String HttpClient::Referer;

    /**
     * @brief Starts a new HTTP session, initializes the cURL API session
     *
     * If a new session was already started, the method has no effect.
     *
     * @param [in] bHTTPS Enable/Disable HTTPS (disabled by default)
     * @param [in] eSettingsFlags optional use | operator to choose multiple options
     *
     * @retval true   Successfully initialized the session.
     * @retval false  The session is already initialized
     * Use CleanupSession() before initializing a new one or the Curl API is not initialized.
     *
     * Example Usage:
     * @code
     *    m_pHTTPClient->InitSession();
     * @endcode
     */
    const bool HttpClient::InitSession(const bool& bHTTPS /* = false */,
        const SettingsFlag& eSettingsFlags /* = ALL_FLAGS */)
    {
        if (m_pCurlSession)
        {
            if (eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_CURL_ALREADY_INIT_MSG);

            return false;
        }
        m_pCurlSession = curl_easy_init();

        m_bHTTPS = bHTTPS;
        m_eSettingsFlags = eSettingsFlags;

        return (m_pCurlSession != nullptr);
    }

    /**
     * @brief Cleans the current HTTP session
     *
     * If a session was not already started, the method has no effect
     *
     * @retval true   Successfully cleaned the current session.
     * @retval false  The session is not already initialized.
     *
     * Example Usage:
     * @code
     *    m_pHTTPClient->CleanupSession();
     * @endcode
     */
    const bool HttpClient::CleanupSession()
    {
        if (!m_pCurlSession)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);

            return false;
        }

        curl_easy_cleanup(m_pCurlSession);
        m_pCurlSession = nullptr;

        if (m_pHeaderlist)
        {
            curl_slist_free_all((struct curl_slist*)m_pHeaderlist);
            m_pHeaderlist = nullptr;
        }

        return true;
    }

    /**
     * @brief sets the progress function callback and the owner of the client
     *
     * @param [in] pOwner pointer to the object owning the client, nullptr otherwise
     * @param [in] fnCallback callback to progress function
     *
     */
    /*inline*/ void HttpClient::SetProgressFnCallback(void* pOwner, const ProgressFnCallback& fnCallback)
    {
        m_ProgressStruct.pOwner = pOwner;
        m_fnProgressCallback = fnCallback;
        m_ProgressStruct.pCurl = m_pCurlSession;
        m_ProgressStruct.dLastRunTime = 0;
        m_bProgressCallbackSet = true;
    }

    /**
     * @brief sets the HTTP Proxy address to tunnel the operation through it
     *
     * @param [in] strProxy URI of the HTTP Proxy
     *
     */
    /*inline*/ void HttpClient::SetProxy(const String& strProxy)
    {
        if (strProxy.empty())
            return;

        String strUri = strProxy;
        std::transform(strUri.begin(), strUri.end(), strUri.begin(), ::toupper);

        if (strUri.compare(0, 4, "HTTP") != 0)
            m_strProxy = "http://" + strProxy;
        else
            m_strProxy = strProxy;
    };

    /**
     * @brief checks a URI
     * adds the proper protocol scheme (HTTP:// or HTTPS://)
     * if the URI has no protocol scheme, the added protocol scheme
     * will depend on m_bHTTPS that can be set when initializing a session
     * or with the SetHTTPS(bool)
     *
     * @param [in] strURL user URI
     */
    inline void HttpClient::UpdateURL(const String& strURL)
    {
        String strTmp = strURL;

        std::transform(strTmp.begin(), strTmp.end(), strTmp.begin(), ::toupper);

        if (strTmp.compare(0, 7, "HTTP://") == 0)
            m_bHTTPS = false;
        else if (strTmp.compare(0, 8, "HTTPS://") == 0)
            m_bHTTPS = true;
        else
        {
            m_strURL = ((m_bHTTPS) ? "https://" : "http://") + strURL;
            return;
        }
        m_strURL = strURL;
    }

    /**
    * @brief performs the chosen HTTP request
    * sets up the common settings (Timeout, proxy,...)
    *
    *
    * @retval true   Successfully performed the request.
    * @retval false  An error occured while CURL was performing the request.
    */
    const int HttpClient::Perform()
    {
        if (!m_pCurlSession)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);

            return CURLE_FAILED_INIT;
        }

        int res = CURLE_OK;

        if (!UserAgent.empty())
            curl_easy_setopt(m_pCurlSession, CURLOPT_USERAGENT, UserAgent.c_str());
        else
            curl_easy_setopt(m_pCurlSession, CURLOPT_USERAGENT, CLIENT_USERAGENT);
        if (!Referer.empty())
            curl_easy_setopt(m_pCurlSession, CURLOPT_REFERER, Referer.c_str());

        curl_easy_setopt(m_pCurlSession, CURLOPT_URL, m_strURL.c_str());

        if (m_pHeaderlist != nullptr)
            curl_easy_setopt(m_pCurlSession, CURLOPT_HTTPHEADER, m_pHeaderlist);

        curl_easy_setopt(m_pCurlSession, CURLOPT_AUTOREFERER, 1L);
        curl_easy_setopt(m_pCurlSession, CURLOPT_FOLLOWLOCATION, 1L);

        if (m_iCurlTimeout > 0)
        {
            curl_easy_setopt(m_pCurlSession, CURLOPT_TIMEOUT, m_iCurlTimeout);
            // don't want to get a sig alarm on timeout
            curl_easy_setopt(m_pCurlSession, CURLOPT_NOSIGNAL, 1);
        }

        if (!m_strProxy.empty())
        {
            curl_easy_setopt(m_pCurlSession, CURLOPT_PROXY, m_strProxy.c_str());
            curl_easy_setopt(m_pCurlSession, CURLOPT_HTTPPROXYTUNNEL, 1L);
        }

        if (m_bNoSignal)
        {
            curl_easy_setopt(m_pCurlSession, CURLOPT_NOSIGNAL, 1L);
        }

        if (m_bProgressCallbackSet)
        {
            curl_easy_setopt(m_pCurlSession, CURLOPT_PROGRESSFUNCTION, *GetProgressFnCallback());
            curl_easy_setopt(m_pCurlSession, CURLOPT_PROGRESSDATA, &m_ProgressStruct);
            curl_easy_setopt(m_pCurlSession, CURLOPT_NOPROGRESS, 0L);
        }

        if (m_bHTTPS)
        {
            // SSL (TLS)
            curl_easy_setopt(m_pCurlSession, CURLOPT_USE_SSL, CURLUSESSL_ALL);
            curl_easy_setopt(m_pCurlSession, CURLOPT_SSL_VERIFYPEER, (m_eSettingsFlags & VERIFY_PEER) ? 1L : 0L);
            curl_easy_setopt(m_pCurlSession, CURLOPT_SSL_VERIFYPEER, (m_eSettingsFlags & CURLOPT_SSL_VERIFYHOST) ? 2L : 0L);
        }

        if (m_bHTTPS && !s_strCertificationAuthorityFile.empty())
            curl_easy_setopt(m_pCurlSession, CURLOPT_CAINFO, s_strCertificationAuthorityFile.c_str());

        if (m_bHTTPS && !m_strSSLCertFile.empty())
            curl_easy_setopt(m_pCurlSession, CURLOPT_SSLCERT, m_strSSLCertFile.c_str());

        if (m_bHTTPS && !m_strSSLKeyFile.empty())
            curl_easy_setopt(m_pCurlSession, CURLOPT_SSLKEY, m_strSSLKeyFile.c_str());

        if (m_bHTTPS && !m_strSSLKeyPwd.empty())
            curl_easy_setopt(m_pCurlSession, CURLOPT_KEYPASSWD, m_strSSLKeyPwd.c_str());

        // Perform the requested operation
        res = curl_easy_perform(m_pCurlSession);

        if (m_pHeaderlist)
        {
            curl_slist_free_all((struct curl_slist*)m_pHeaderlist);
            m_pHeaderlist = nullptr;
        }

        return res;
    }

    /**
     * @brief requests the content of a URI
     *
     * @param [in] strURL URI of the remote location (with the file name) encoded in UTF-8 format.
     * @param [out] strOutput reference to an output string.
     * @param [out] lHTTPStatusCode HTTP Status code of the response.
     *
     * @retval true   Successfully requested the URI.
     * @retval false  Encountered a problem.
     *
     * Example Usage:
     * @code
     *    String strWebPage;
     *    long lHTTPStatusCode = 0;
     *    m_pHTTOClient->GetText("https://www.google.com", strWebPage, lHTTPStatusCode);
     * @endcode
     */
    const bool HttpClient::GetText(const String& strURL,
        String& strOutput,
        long& lHTTPStatusCode)
    {
        if (strURL.empty())
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_EMPTY_HOST_MSG);

            return false;
        }
        if (!m_pCurlSession)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);

            return false;
        }
        // Reset is mandatory to avoid bad surprises
        curl_easy_reset(m_pCurlSession);

        UpdateURL(strURL);

        curl_easy_setopt(m_pCurlSession, CURLOPT_HTTPGET, 1L);
        curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteInStringCallback);
        curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &strOutput);

        int res = Perform();

        curl_easy_getinfo(m_pCurlSession, CURLINFO_RESPONSE_CODE, &lHTTPStatusCode);

        // Check for errors
        if (res != CURLE_OK)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(StringFormat(LOG_ERROR_CURL_REQ_FAILURE_FORMAT, m_strURL.c_str(), res,
                    curl_easy_strerror((CURLcode)res), lHTTPStatusCode));

            return false;
        }

        return true;
    }

    /**
     * @brief Downloads a remote file to a local file.
     *
     * @param [in] strLocalFile Complete path of the local file to download in UTF-8 format.
     * @param [in] strURL URI of the remote location (with the file name) encoded in UTF-8 format.
     * @param [out] lHTTPStatusCode HTTP Status code of the response.
     *
     * @retval true   Successfully downloaded the file.
     * @retval false  The file couldn't be downloaded. Check the log messages for more information.
     */
    const bool HttpClient::DownloadFile(const String& strLocalFile,
        const String& strURL,
        long& lHTTPStatusCode)
    {
        if (strURL.empty() || strLocalFile.empty())
            return false;

        if (!m_pCurlSession)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);

            return false;
        }
        // Reset is mandatory to avoid bad surprises
        curl_easy_reset(m_pCurlSession);

        UpdateURL(strURL);

        std::ofstream ofsOutput;
        ofsOutput.open(
#ifdef LINUX
            strLocalFile, // UTF-8
#else
            StringUtil::UTF8toUTF16(strLocalFile),
#endif
            std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);

        if (ofsOutput)
        {
            curl_easy_setopt(m_pCurlSession, CURLOPT_BUFFERSIZE, 1048576L);
            curl_easy_setopt(m_pCurlSession, CURLOPT_HTTPGET, 1L);
            curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteToFileCallback);
            curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &ofsOutput);

            int res = Perform();

            ofsOutput.close();
            curl_easy_getinfo(m_pCurlSession, CURLINFO_RESPONSE_CODE, &lHTTPStatusCode);

            //double dUploadLength = 0;
            //curl_easy_getinfo(m_pCurlSession, CURLINFO_CONTENT_LENGTH_UPLOAD, &dUploadLength); // number of bytes uploaded

            /* Delete downloaded file if status code != 200 as server's response body may
            contain error 404 */
            if (lHTTPStatusCode != 200)
                remove(strLocalFile.c_str());

            if (res != CURLE_OK)
            {
                if (m_eSettingsFlags & ENABLE_LOG)
                    m_oLog(StringFormat(LOG_ERROR_CURL_DOWNLOAD_FAILURE_FORMAT, strLocalFile.c_str(),
                        strURL.c_str(), res, curl_easy_strerror((CURLcode)res), lHTTPStatusCode));

                return false;
            }
        }
        else if (m_eSettingsFlags & ENABLE_LOG)
        {
            m_oLog(StringFormat(LOG_ERROR_DOWNLOAD_FILE_FORMAT, strLocalFile.c_str()));

            return false;
        }

        return true;
    }

    /**
     * @brief downloads a remote file to memory
     *
     * @param [out] data vector of bytes
     * @param [in] strURL URI of the remote location (with the file name) encoded in UTF-8 format.
     * @param [out] lHTTPStatusCode HTTP Status code of the response.
     *
     * @retval true   Successfully downloaded the file.
     * @retval false  The content couldn't be downloaded. Check the log messages for
     * more information.
     */
    const bool HttpClient::DownloadFile(std::vector<unsigned char>& data, const String& strURL, long& lHTTPStatusCode) {
        if (strURL.empty())
            return false;

        if (!m_pCurlSession)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);

            return false;
        }

        data.clear();

        // Reset is mandatory to avoid bad surprises
        curl_easy_reset(m_pCurlSession);

        UpdateURL(strURL);

        curl_easy_setopt(m_pCurlSession, CURLOPT_HTTPGET, 1L);
        curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteToMemoryCallback);
        curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &data);

        int res = Perform();

        curl_easy_getinfo(m_pCurlSession, CURLINFO_RESPONSE_CODE, &lHTTPStatusCode);

        if (res != CURLE_OK)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(StringFormat(LOG_ERROR_CURL_DOWNLOAD_FAILURE_FORMAT, "Download to a byte buffer",
                    strURL.c_str(), res, curl_easy_strerror((CURLcode)res), lHTTPStatusCode));

            return false;
        }

        return true;
    }

    void HttpClient::AddHeader(const String& strHeader)
    {
        m_pHeaderlist = curl_slist_append((struct curl_slist*)m_pHeaderlist, strHeader.c_str());
    }

    /**
     * @brief uploads a POST form
     *
     *
     * @param [in] strURL URL to which the form will be posted encoded in UTF-8 format.
     * @param [in] data post form information
     * @param [out] lHTTPStatusCode HTTP Status code of the response.
     *
     * @retval true   Successfully posted the header.
     * @retval false  The header couldn't be posted.
     */
    const bool HttpClient::UploadForm(const String& strURL,
        const PostFormInfo& data,
        long& lHTTPStatusCode)
    {
        if (strURL.empty())
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_EMPTY_HOST_MSG);

            return false;
        }
        if (!m_pCurlSession)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);

            return false;
        }
        // Reset is mandatory to avoid bad surprises
        curl_easy_reset(m_pCurlSession);

        UpdateURL(strURL);

        /** Now specify we want to POST data */
        curl_easy_setopt(m_pCurlSession, CURLOPT_POST, 1L);

        /* stating that Expect: 100-continue is not wanted */
        AddHeader("Expect:");

        /** set post form */
        if (data.m_pFormPost != nullptr)
            curl_easy_setopt(m_pCurlSession, CURLOPT_HTTPPOST, data.m_pFormPost);

        /* to avoid printing response's body to stdout.
         * CURLOPT_WRITEDATA : by default, this is a FILE * to stdout. */
        curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, ThrowAwayCallback);

        int res = Perform();

        curl_easy_getinfo(m_pCurlSession, CURLINFO_RESPONSE_CODE, &lHTTPStatusCode);

        // Check for errors
        if (res != CURLE_OK)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(StringFormat(LOG_ERROR_CURL_REQ_FAILURE_FORMAT, m_strURL.c_str(), res,
                    curl_easy_strerror((CURLcode)res), lHTTPStatusCode));

            return false;
        }

        return true;
    }

    /**
     * @brief PostFormInfo constructor
     */
    HttpClient::PostFormInfo::PostFormInfo() :
        m_pFormPost(nullptr), m_pLastFormptr(nullptr)
    {
    }

    /**
     * @brief PostFormInfo destructor
     */
    HttpClient::PostFormInfo::~PostFormInfo()
    {
        // cleanup the formpost chain
        if (m_pFormPost)
        {
            curl_formfree((curl_httppost*)m_pFormPost);
            m_pFormPost = nullptr;
            m_pLastFormptr = nullptr;
        }
    }

    /**
     * @brief set the name and the value of the HTML "file" form's input
     *
     * @param fieldName name of the "file" input encoded in UTF8.
     * @param fieldValue path to the file to upload encoded in UTF8.
     */
    void HttpClient::PostFormInfo::AddFormFile(const String& strFieldName,
        const String& strFieldValue)
    {
        curl_formadd((curl_httppost**)&m_pFormPost, (curl_httppost**)&m_pLastFormptr,
            CURLFORM_COPYNAME, strFieldName.c_str(),
            CURLFORM_FILE, strFieldValue.c_str(),
            CURLFORM_END);
    }

    /**
     * @brief set the name and the value of an HTML form's input
     * (other than "file" like "text", "hidden" or "submit")
     *
     * @param fieldName name of the input element encoded in UTF8 for Linux and in ANSI for Windows (so the file gets located and uploaded).
     * @param fieldValue value to be assigned to the input element encoded in UTF8 for Linux and in ANSI for Windows.
     */
    void HttpClient::PostFormInfo::AddFormContent(const String& strFieldName,
        const String& strFieldValue)
    {
        curl_formadd((curl_httppost**)&m_pFormPost, (curl_httppost**)&m_pLastFormptr,
            CURLFORM_COPYNAME, strFieldName.c_str(),
            CURLFORM_COPYCONTENTS, strFieldValue.c_str(),
            CURLFORM_END);
    }

    // REST REQUESTS

    /**
    * @brief initializes a REST request
    * some common operations to REST requests are performed here,
    * the others are performed in Perform method
    *
    * @param [in] strUrl URI encoded in UTF-8 format.
    * @param [in] Headers headers to send
    * @param [out] Response response data
    */
    inline const bool HttpClient::InitRestRequest(const String& strUrl,
        const HttpClient::HeadersMap& Headers,
        HttpClient::HttpResponse& Response)
    {
        if (strUrl.empty())
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_EMPTY_HOST_MSG);

            return false;
        }
        if (!m_pCurlSession)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);

            return false;
        }
        // Reset is mandatory to avoid bad surprises
        curl_easy_reset(m_pCurlSession);

        UpdateURL(strUrl);

        // set the received body's callback function
        curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, &HttpClient::RestWriteCallback);

        // set data object to pass to callback function above
        curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &Response);

        // set the response's headers processing callback function
        curl_easy_setopt(m_pCurlSession, CURLOPT_HEADERFUNCTION, &HttpClient::RestHeaderCallback);

        // callback object for server's responses headers
        curl_easy_setopt(m_pCurlSession, CURLOPT_HEADERDATA, &Response);

        String strHeader;
        for (HeadersMap::const_iterator it = Headers.cbegin();
            it != Headers.cend();
            ++it)
        {
            strHeader = it->first + ": " + it->second; // build header string
            AddHeader(strHeader);
        }

        return true;
    }

    /**
    * @brief post REST request operations are performed here
    *
    * @param [in] ePerformCode curl easy perform returned code
    * @param [out] Response response data
    */
    inline const bool HttpClient::PostRestRequest(const int ePerformCode,
        HttpClient::HttpResponse& Response)
    {
        // Check for errors
        if (ePerformCode != CURLE_OK)
        {
            Response.strBody.clear();
            Response.iCode = -1;

            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(StringFormat(LOG_ERROR_CURL_REST_FAILURE_FORMAT, m_strURL.c_str(), ePerformCode,
                    curl_easy_strerror((CURLcode)ePerformCode)));

            return false;
        }
        long lHttpCode = 0;
        curl_easy_getinfo(m_pCurlSession, CURLINFO_RESPONSE_CODE, &lHttpCode);
        Response.iCode = static_cast<int>(lHttpCode);

        return true;
    }

    /**
    * @brief performs a HEAD request
    *
    * @param [in] strUrl url to request encoded in UTF-8 format.
    * @param [in] Headers headers to send
    * @param [out] Response response data
    *
    * @retval true   Successfully requested the URI.
    * @retval false  Encountered a problem.
    */
    const bool HttpClient::Head(const String& strUrl,
        const HttpClient::HeadersMap& Headers,
        HttpClient::HttpResponse& Response)
    {
        if (InitRestRequest(strUrl, Headers, Response))
        {
            /** set HTTP HEAD METHOD */
            curl_easy_setopt(m_pCurlSession, CURLOPT_CUSTOMREQUEST, "HEAD");
            curl_easy_setopt(m_pCurlSession, CURLOPT_NOBODY, 1L);

            int res = Perform();

            return PostRestRequest(res, Response);
        }
        else
            return false;
    }

    /**
    * @brief performs a GET request
    *
    * @param [in] strUrl url to request encoded in UTF-8 format.
    * @param [in] Headers headers to send
    * @param [out] Response response data
    *
    * @retval true   Successfully requested the URI.
    * @retval false  Encountered a problem.
    */
    const bool HttpClient::Get(const String& strUrl,
        const HttpClient::HeadersMap& Headers,
        HttpClient::HttpResponse& Response)
    {
        if (InitRestRequest(strUrl, Headers, Response))
        {
            // specify a GET request
            curl_easy_setopt(m_pCurlSession, CURLOPT_HTTPGET, 1L);

            int res = Perform();

            return PostRestRequest(res, Response);
        }
        else
            return false;
    }

    /**
    * @brief performs a DELETE request
    *
    * @param [in] strUrl url to request encoded in UTF-8 format.
    * @param [in] Headers headers to send
    * @param [out] Response response data
    *
    * @retval true   Successfully requested the URI.
    * @retval false  Encountered a problem.
    */
    const bool HttpClient::Del(const String& strUrl,
        const HttpClient::HeadersMap& Headers,
        HttpClient::HttpResponse& Response)
    {
        if (InitRestRequest(strUrl, Headers, Response))
        {
            curl_easy_setopt(m_pCurlSession, CURLOPT_CUSTOMREQUEST, "DELETE");

            int res = Perform();

            return PostRestRequest(res, Response);
        }
        else
            return false;
    }

    const bool HttpClient::Post(const String& strUrl,
        const HttpClient::HeadersMap& Headers,
        const String& strPostData,
        HttpClient::HttpResponse& Response)
    {
        if (InitRestRequest(strUrl, Headers, Response))
        {
            // specify a POST request
            curl_easy_setopt(m_pCurlSession, CURLOPT_POST, 1L);

            // set post informations
            curl_easy_setopt(m_pCurlSession, CURLOPT_POSTFIELDS, strPostData.c_str());
            curl_easy_setopt(m_pCurlSession, CURLOPT_POSTFIELDSIZE, strPostData.size());

            int res = Perform();

            return PostRestRequest(res, Response);
        }
        else
            return false;
    }

    /**
    * @brief performs a PUT request with a string
    *
    * @param [in] strUrl url to request encoded in UTF-8 format.
    * @param [in] Headers headers to send
    * @param [out] Response response data
    *
    * @retval true   Successfully requested the URI.
    * @retval false  Encountered a problem.
    */
    const bool HttpClient::Put(const String& strUrl, const HttpClient::HeadersMap& Headers,
        const String& strPutData, HttpClient::HttpResponse& Response)
    {
        if (InitRestRequest(strUrl, Headers, Response))
        {
            HttpClient::UploadObject Payload;

            Payload.pszData = strPutData.c_str();
            Payload.usLength = strPutData.size();

            // specify a PUT request
            curl_easy_setopt(m_pCurlSession, CURLOPT_PUT, 1L);
            curl_easy_setopt(m_pCurlSession, CURLOPT_UPLOAD, 1L);

            // set read callback function
            curl_easy_setopt(m_pCurlSession, CURLOPT_READFUNCTION, &HttpClient::RestReadCallback);
            // set data object to pass to callback function
            curl_easy_setopt(m_pCurlSession, CURLOPT_READDATA, &Payload);

            // set data size
            curl_easy_setopt(m_pCurlSession, CURLOPT_INFILESIZE, static_cast<long>(Payload.usLength));

            int res = Perform();

            return PostRestRequest(res, Response);
        }
        else
            return false;
    }

    /**
    * @brief performs a PUT request with a byte buffer (vector of char)
    *
    * @param [in] strUrl url to request encoded in UTF-8 format.
    * @param [in] Headers headers to send
    * @param [out] Response response data
    *
    * @retval true   Successfully requested the URI.
    * @retval false  Encountered a problem.
    */
    const bool HttpClient::Put(const String& strUrl, const HttpClient::HeadersMap& Headers,
        const HttpClient::ByteBuffer& Data, HttpClient::HttpResponse& Response)
    {
        if (InitRestRequest(strUrl, Headers, Response))
        {
            HttpClient::UploadObject Payload;

            Payload.pszData = Data.data();
            Payload.usLength = Data.size();

            // specify a PUT request
            curl_easy_setopt(m_pCurlSession, CURLOPT_PUT, 1L);
            curl_easy_setopt(m_pCurlSession, CURLOPT_UPLOAD, 1L);

            // set read callback function
            curl_easy_setopt(m_pCurlSession, CURLOPT_READFUNCTION, &HttpClient::RestReadCallback);
            // set data object to pass to callback function
            curl_easy_setopt(m_pCurlSession, CURLOPT_READDATA, &Payload);

            // set data size
            curl_easy_setopt(m_pCurlSession, CURLOPT_INFILESIZE, static_cast<long>(Payload.usLength));

            int res = Perform();

            return PostRestRequest(res, Response);
        }
        else
            return false;
    }

    /**
    * @brief removes leading and trailing whitespace from a string
    *
    * @param [in/out] str string to be trimmed
    */
    inline void HttpClient::TrimSpaces(String& str)
    {
        // trim from left
        str.erase(str.begin(),
            std::find_if(str.begin(), str.end(), [](char c) {return !isspace(c); })
        );

        // trim from right
        str.erase(std::find_if(str.rbegin(), str.rend(), [](char c) {return !isspace(c); }).base(),
            str.end()
        );
    }

    // CURL CALLBACKS

    size_t HttpClient::ThrowAwayCallback(void* ptr, size_t size, size_t nmemb, void* data)
    {
        reinterpret_cast<void*>(ptr);
        reinterpret_cast<void*>(data);

        /* we are not interested in the headers itself,
        so we only return the size we would have saved ... */
        return size * nmemb;
    }

    /**
    * @brief stores the server response in a string
    *
    * @param ptr pointer of max size (size*nmemb) to read data from it
    * @param size size parameter
    * @param nmemb memblock parameter
    * @param data pointer to user data (string)
    *
    * @return (size * nmemb)
    */
    size_t HttpClient::WriteInStringCallback(void* ptr, size_t size, size_t nmemb, void* data)
    {
        String* strWriteHere = reinterpret_cast<String*>(data);
        if (strWriteHere != nullptr)
        {
            strWriteHere->append(reinterpret_cast<char*>(ptr), size * nmemb);
            return size * nmemb;
        }
        return 0;
    }

    /**
    * @brief reads the content of an already opened file stream
    * used by UploadFile()
    *
    * @param ptr pointer of max size (size*nmemb) to write data to it
    * @param size size parameter
    * @param nmemb memblock parameter
    * @param stream pointer to user data (file stream)
    *
    * @return (size * nmemb)
    */
    size_t HttpClient::ReadFromFileCallback(void* ptr, size_t size, size_t nmemb, void* stream)
    {
        std::ifstream* pFileStream = reinterpret_cast<std::ifstream*>(stream);
        if (pFileStream->is_open())
        {
            pFileStream->read(reinterpret_cast<char*>(ptr), size * nmemb);
            return pFileStream->gcount();
        }
        return 0;
    }

    // REST CALLBACKS

    /**
    * @brief write callback function for libcurl
    * this callback will be called to store the server's Body reponse
    * in a struct response
    *
    * we can also use an std::vector<char> instead of an String but in this case
    * there isn't a big difference... maybe resizing the container with a max size can
    * enhance performances...
    *
    * @param data returned data of size (size*nmemb)
    * @param size size parameter
    * @param nmemb memblock parameter
    * @param userdata pointer to user data to save/work with return data
    *
    * @return (size * nmemb)
    */
    size_t HttpClient::RestWriteCallback(void* pCurlData, size_t usBlockCount, size_t usBlockSize, void* pUserData)
    {
        HttpClient::HttpResponse* pServerResponse;
        pServerResponse = reinterpret_cast<HttpClient::HttpResponse*>(pUserData);
        pServerResponse->strBody.append(reinterpret_cast<char*>(pCurlData), usBlockCount * usBlockSize);
        return (usBlockCount * usBlockSize);
    }

    /**
    * @brief header callback for libcurl
    * callback used to process response's headers (received)
    *
    * @param data returned (header line)
    * @param size of data
    * @param nmemb memblock
    * @param userdata pointer to user data object to save header data
    * @return size * nmemb;
    */
    size_t HttpClient::RestHeaderCallback(void* pCurlData, size_t usBlockCount, size_t usBlockSize, void* pUserData)
    {
        HttpClient::HttpResponse* pServerResponse;
        pServerResponse = reinterpret_cast<HttpClient::HttpResponse*>(pUserData);

        String strHeader(reinterpret_cast<char*>(pCurlData), usBlockCount * usBlockSize);
        size_t usSeperator = strHeader.find_first_of(":");
        if (String::npos == usSeperator)
        {
            //roll with non seperated headers or response's line
            TrimSpaces(strHeader);
            if (0 == strHeader.length())
            {
                return (usBlockCount * usBlockSize); //blank line;
            }
            pServerResponse->mapHeaders[strHeader] = "present";
        }
        else
        {
            String strKey = strHeader.substr(0, usSeperator);
            TrimSpaces(strKey);
            String strValue = strHeader.substr(usSeperator + 1);
            TrimSpaces(strValue);
            pServerResponse->mapHeaders[strKey] = strValue;
        }

        return (usBlockCount * usBlockSize);
    }

    /**
    * @brief read callback function for libcurl
    * used to send (or upload) a content to the server
    *
    * @param pointer of max size (size*nmemb) to write data to (used by cURL to send data)
    * @param size size parameter
    * @param nmemb memblock parameter
    * @param userdata pointer to user data to read data from
    *
    * @return (size * nmemb)
    */
    size_t HttpClient::RestReadCallback(void* pCurlData, size_t usBlockCount, size_t usBlockSize, void* pUserData)
    {
        // get upload struct
        HttpClient::UploadObject* Payload;

        Payload = reinterpret_cast<HttpClient::UploadObject*>(pUserData);

        // set correct sizes
        size_t usCurlSize = usBlockCount * usBlockSize;
        size_t usCopySize = (Payload->usLength < usCurlSize) ? Payload->usLength : usCurlSize;

        /** copy data to buffer */
        std::memcpy(pCurlData, Payload->pszData, usCopySize);

        // decrement length and increment data pointer
        Payload->usLength -= usCopySize; // remaining bytes to be sent
        Payload->pszData += usCopySize;  // next byte to the chunk that will be sent

        /** return copied size */
        return usCopySize;
    }

    // CURL DEBUG INFO CALLBACKS
#ifdef DEBUG_CURL
    int DebugCallback(CURL* curl, curl_infotype curl_info_type, char* pszTrace, size_t usSize, void* pFile)
    {
        String strText;
        String strTrace(pszTrace, usSize);

        switch (curl_info_type)
        {
        case CURLINFO_TEXT:
            strText = "# Information : ";
            break;
        case CURLINFO_HEADER_OUT:
            strText = "-> Sending header : ";
            break;
        case CURLINFO_DATA_OUT:
            strText = "-> Sending data : ";
            break;
        case CURLINFO_SSL_DATA_OUT:
            strText = "-> Sending SSL data : ";
            break;
        case CURLINFO_HEADER_IN:
            strText = "<- Receiving header : ";
            break;
        case CURLINFO_DATA_IN:
            strText = "<- Receiving unencrypted data : ";
            break;
        case CURLINFO_SSL_DATA_IN:
            strText = "<- Receiving SSL data : ";
            break;
        default:
            break;
        }

        std::ofstream* pofTraceFile = reinterpret_cast<std::ofstream*>(pFile);
        if (pofTraceFile == nullptr)
        {
            std::cout << "[DEBUG] cURL debug log [" << curl_info_type << "]: " << " - " << strTrace;
        }
        else
        {
            (*pofTraceFile) << strText << strTrace;
        }

        return 0;
    }

    void StartCurlDebug(CURL* m_pCurlSession)
    {
        curl_easy_setopt(m_pCurlSession, CURLOPT_VERBOSE, 1L);
        curl_easy_setopt(m_pCurlSession, CURLOPT_DEBUGFUNCTION, DebugCallback);

        String strFileCurlTraceFullName("curl_debug.log");
        if (!strFileCurlTraceFullName.empty())
        {
            char szDate[32];
            memset(szDate, 0, 32);
            time_t tNow; time(&tNow);
            // new trace file for each hour
            strftime(szDate, 32, "%Y%m%d_%H", localtime(&tNow));
            strFileCurlTraceFullName += "TraceLog_";
            strFileCurlTraceFullName += szDate;
            strFileCurlTraceFullName += ".txt";

            m_ofFileCurlTrace.open(strFileCurlTraceFullName, std::ifstream::app | std::ifstream::binary);

            if (m_ofFileCurlTrace)
                curl_easy_setopt(m_pCurlSession, CURLOPT_DEBUGDATA, &m_ofFileCurlTrace);
        }
    }

    void HttpClient::EndCurlDebug() const
    {
        if (m_ofFileCurlTrace && m_ofFileCurlTrace.is_open())
        {
            m_ofFileCurlTrace << "###########################################" << std::endl;
            m_ofFileCurlTrace.close();
        }
    }
#endif

    HttpClientEx::HttpClientEx()
        :HttpClient([](const String& logMessage) {/* do nothing.*/})
    {
        InitSession();
    }

    HttpClientEx::HttpClientEx(LogFnCallback oLogger)
        :HttpClient(oLogger)
    {
        InitSession();
    }

    HttpClientEx::~HttpClientEx()
    {
        CleanupSession();
    }

    String HttpClientEx::fetchContent(String url,long& retCode)
    {
        String content;
        bool ret = GetText(url, content, retCode);
        if (!ret)
        {
            content = "";
        }

        return content;
    }

    bool HttpClientEx::fetchFile(String url, String fileName)
    {
        long retCode = 0;
        String content = fetchContent(url, retCode);
        if (content.size() > 0 && retCode == 200)
        {
            std::ofstream ofs(fileName, std::ios::binary | std::ios::ate | std::ios::out);
            if (ofs) 
            {
                ofs.write(content.data(), content.size());
                ofs.close();
                return true;
            }
        }
        return false;
    }

    FtpClient::FtpClient(LogFnCallback oLogger)
        :m_curlHandle(CurlHandle::instance())
        , m_pCurlSession(nullptr)
        , m_oLog(oLogger)
        , m_iCurlTimeout(0)
    {

    }

    FtpClient::~FtpClient()
    {
        if (m_pCurlSession != nullptr)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_WARNING_OBJECT_NOT_CLEANED);

            CleanupSession();
        }
    }

    const bool FtpClient::InitSession(const SettingsFlag& eSettingsFlags)
    {
        if (m_pCurlSession)
        {
            if (eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_CURL_ALREADY_INIT_MSG);

            return false;
        }
        m_pCurlSession = curl_easy_init();
        m_eSettingsFlags = eSettingsFlags;

        curl_easy_reset(m_pCurlSession);

        curl_easy_setopt(m_pCurlSession, CURLOPT_HEADERFUNCTION, getContentLengthFunc);
        if (!m_strUser.empty() && !m_strPswd.empty())
        {
            String usrPswKey = m_strUser + ":" + m_strPswd;
            curl_easy_setopt(m_pCurlSession, CURLOPT_USERPWD, usrPswKey.c_str());
        }

        curl_easy_setopt(m_pCurlSession, CURLOPT_HEADERDATA, &m_lFileSize);

        return (m_pCurlSession != nullptr);
    }

    const bool FtpClient::CleanupSession()
    {
        if (!m_pCurlSession)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);

            return false;
        }

        curl_easy_cleanup(m_pCurlSession);
        m_pCurlSession = nullptr;

        return true;
    }

    const bool FtpClient::DownloadMem(String& data,
        const String& strURL,
        long& lHTTPStatusCode)
    {
        if (strURL.empty())
            return false;

        UpdateURL(strURL);

        curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteToMemoryCallback2);
        curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &data);

        int res = Perform();

        curl_easy_getinfo(m_pCurlSession, CURLINFO_RESPONSE_CODE, &lHTTPStatusCode);

        if (res != CURLE_OK)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(StringFormat(LOG_ERROR_CURL_DOWNLOAD_FAILURE_FORMAT, "Download to a byte buffer",
                    strURL.c_str(), res, curl_easy_strerror((CURLcode)res), lHTTPStatusCode));

            return false;
        }

        return true;
    }
    const bool FtpClient::UploadFile(const String& strLocalFile,
        const String& strURL,
        long& lStatusCode)
    {
        if (strURL.empty() || strLocalFile.empty())
            return false;

        if (!m_pCurlSession)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);

            return false;
        }

        UpdateURL(strURL);

        m_lFileSize = 0;

        std::ifstream ifstream;
        ifstream.open(
#ifdef LINUX
            strLocalFile, // UTF-8
#else
            StringUtil::UTF8toUTF16(strLocalFile),
#endif
            std::ofstream::binary);

        if (ifstream)
        {
            curl_easy_setopt(m_pCurlSession, CURLOPT_UPLOAD_BUFFERSIZE, 1048576L);
            curl_easy_setopt(m_pCurlSession, CURLOPT_UPLOAD, 1L);
            curl_easy_setopt(m_pCurlSession, CURLOPT_READFUNCTION, &HttpClient::ReadFromFileCallback);
            curl_easy_setopt(m_pCurlSession, CURLOPT_READDATA, &ifstream);

            int res = Perform();
            ifstream.close();

            curl_easy_getinfo(m_pCurlSession, CURLINFO_RESPONSE_CODE, &lStatusCode);

            if (res != CURLE_OK)
            {
                if (m_eSettingsFlags & ENABLE_LOG)
                    m_oLog(StringFormat(LOG_ERROR_CURL_DOWNLOAD_FAILURE_FORMAT, strLocalFile.c_str(),
                        strURL.c_str(), res, curl_easy_strerror((CURLcode)res), lStatusCode));

                return false;
            }
        }
        else if (m_eSettingsFlags & ENABLE_LOG)
        {
            m_oLog(StringFormat(LOG_ERROR_DOWNLOAD_FILE_FORMAT, strLocalFile.c_str()));

            return false;
        }

        return true;
    }

    const bool FtpClient::DownloadFile(const String& strLocalFile,
        const String& strURL,
        long& lStatusCode)
    {
        if (strURL.empty() || strLocalFile.empty())
            return false;

        if (!m_pCurlSession)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);

            return false;
        }

        UpdateURL(strURL);

        m_lFileSize = 0;

        std::ofstream ofsOutput;
        ofsOutput.open(
#ifdef LINUX
            strLocalFile, // UTF-8
#else
            StringUtil::UTF8toUTF16(strLocalFile),
#endif
            std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);

        if (ofsOutput)
        {
            curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteToFileCallback);
            curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &ofsOutput);

            int res = Perform();

            ofsOutput.close();

            curl_easy_getinfo(m_pCurlSession, CURLINFO_RESPONSE_CODE, &lStatusCode);

            if (lStatusCode != 226)
                remove(strLocalFile.c_str());

            if (res != CURLE_OK)
            {
                if (m_eSettingsFlags & ENABLE_LOG)
                    m_oLog(StringFormat(LOG_ERROR_CURL_DOWNLOAD_FAILURE_FORMAT, strLocalFile.c_str(),
                        strURL.c_str(), res, curl_easy_strerror((CURLcode)res), lStatusCode));

                return false;
            }
        }
        else if (m_eSettingsFlags & ENABLE_LOG)
        {
            m_oLog(StringFormat(LOG_ERROR_DOWNLOAD_FILE_FORMAT, strLocalFile.c_str()));

            return false;
        }

        return true;
    }

    void FtpClient::UpdateURL(const String& strURL)
    {
        m_strURL = strURL;
    }

    const int FtpClient::Perform()
    {
        if (!m_pCurlSession)
        {
            if (m_eSettingsFlags & ENABLE_LOG)
                m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);

            return CURLE_FAILED_INIT;
        }

        int res = CURLE_OK;

        curl_easy_setopt(m_pCurlSession, CURLOPT_URL, m_strURL.c_str());

        curl_easy_setopt(m_pCurlSession, CURLOPT_FOLLOWLOCATION, 1L);

        if (m_iCurlTimeout > 0)
        {
            curl_easy_setopt(m_pCurlSession, CURLOPT_TIMEOUT, m_iCurlTimeout);
            // don't want to get a sig alarm on timeout
            curl_easy_setopt(m_pCurlSession, CURLOPT_NOSIGNAL, 1);
        }

        // Perform the requested operation
        res = curl_easy_perform(m_pCurlSession);

        return res;
    }


    FtpClientEx::FtpClientEx(String user, String pswd)
        :FtpClientEx(user, pswd,[](const String& logMessage) {/* do nothing.*/})
    {

    }

    FtpClientEx::FtpClientEx(String user, String pswd,LogFnCallback oLogger)
        :FtpClient(oLogger)
    {
        m_strUser = user;
        m_strPswd = pswd;

        InitSession();
    }

    FtpClientEx::~FtpClientEx()
    {
        CleanupSession();
    }

    const StringVector FtpClientEx::getFileList(const String& strURL)
    {
        StringVector fileList;

        long lRetCode = 0;
        String fileListStr;

        if (FtpClient::DownloadMem(fileListStr, strURL, lRetCode))
        {
            StringVector list = StringUtil::split(fileListStr, "\r\n");
            for (auto& fileName : list)
            {
                StringVector lineStr = StringUtil::split(fileName);
                if (lineStr.size() > 0)
                {
                    String folder = lineStr[lineStr.size() - 1];
                    if(!folder.empty())
                        fileList.push_back(folder);
                }
            }
        }

        return fileList;
    }

    const bool FtpClientEx::UploadFile(const String& strURL, const String& strFile)
    {
        long lRetCode;
        return FtpClient::UploadFile(strFile, strURL, lRetCode);
    }

    const bool FtpClientEx::DownloadToPath(const String& strURL, const String& strPath)
    {
        String name, path;
        StringUtil::splitFilename(strURL, name, path);
        String fileName = strPath + "/" + name;
        long lRetCode;
        return FtpClient::DownloadFile(fileName, strURL, lRetCode);
    }
}

